# -*- coding: utf-8 -*-
# Copyright Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
import inspect
import logging
from contextlib import contextmanager
from functools import wraps
from logging import Logger

from typing_extensions import Any, Callable, Type, Union

from msmodelslim.utils.exception import SchemaValidateError, ToDoError


class MsgConst:
    """
    Class for log messages const
    """
    SPECIAL_CHAR = ["\n", "\r", "\u007F", "\b", "\f", "\t", "\u000B", "%08", "%0a", "%0b", "%0c", "%0d", "%7f"]


def filter_special_chars(func):
    @wraps(func)
    def func_level(msg, *args):
        for char in MsgConst.SPECIAL_CHAR:
            if isinstance(msg, str):
                msg = msg.replace(char, ' ')
        return func(msg, *args)

    return func_level


def filter_logger(cur_logger: Logger):
    setattr(cur_logger, 'critical', filter_special_chars(cur_logger.critical))
    setattr(cur_logger, 'debug', filter_special_chars(cur_logger.debug))
    setattr(cur_logger, 'error', filter_special_chars(cur_logger.error))
    setattr(cur_logger, 'info', filter_special_chars(cur_logger.info))
    setattr(cur_logger, 'warning', filter_special_chars(cur_logger.warning))


def get_logger(name: str = ''):
    """
    获取指定名称的日志记录器。
    
    如果名称为空，返回当前全局日志记录器（初始为根日志记录器，可能已被logger_setter更改）。
    如果指定了名称，会获取或创建对应名称的日志记录器并应用特殊字符过滤功能。
    
    Args:
        name (str, optional): 日志记录器的名称。默认为空字符串。
        
    Returns:
        Logger: 配置好的日志记录器实例。
        
    Examples:
        >>> # 获取当前全局日志记录器（初始为根日志记录器，可能已被logger_setter更改）
        >>> current_logger = get_logger()
        
        >>> # 获取指定名称的日志记录器（如果已存在则返回已存在的）
        >>> custom_logger = get_logger("msmodelslim.utils.logging")
        
        >>> # 使用日志记录器
        >>> custom_logger.info("这是一条信息日志")
    """
    if not name:
        return logger
    cur_logger = logging.getLogger(name)
    filter_logger(cur_logger)
    return cur_logger


def get_root_logger():
    root_logger = logging.getLogger(__name__.split('.')[0])
    root_logger.propagate = False
    root_logger.setLevel(logging.INFO)
    filter_logger(root_logger)
    if not root_logger.handlers:
        stream_handler = logging.StreamHandler()
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        stream_handler.setFormatter(formatter)
        root_logger.addHandler(stream_handler)
    return root_logger


logger = get_root_logger()

LOG_LEVEL = {
    "notset": logging.NOTSET,
    "debug": logging.DEBUG,
    "info": logging.INFO,
    "warn": logging.WARN,
    "warning": logging.WARNING,
    "error": logging.ERROR,
    "fatal": logging.FATAL,
    "critical": logging.CRITICAL
}

LOGGER_FUNC = {
    "debug": lambda msg: logger.debug(msg),
    "info": lambda msg: logger.info(msg),
    "warn": lambda msg: logger.warning(msg),
    "warning": lambda msg: logger.warning(msg),
    "error": lambda msg: logger.error(msg),
    "critical": lambda msg: logger.critical(msg),
}


def set_logger_level(level="info"):
    if not isinstance(level, str):
        raise SchemaValidateError(f"level must be str, not {type(level)}",
                                  action='Please make sure log level is a string')
    if level.lower() in LOG_LEVEL:
        logger.setLevel(LOG_LEVEL.get(level.lower()))
    else:
        logger.warning("Set %s log level failed.", level)


def progress_bar(iterable, desc: str = None, total: int = -1, interval: int = 1):
    if total == -1 and hasattr(iterable, "__len__"):
        total = len(iterable)

    format_str = "" if desc is None else (desc + ": ")
    if isinstance(total, int) and total > 0:
        format_str += "[%d/{}]".format(total)
    else:
        format_str += "[%d]"

    if not (isinstance(interval, int) and interval > 0):
        interval = 1

    prev_terminator = logging.StreamHandler.terminator
    logging.StreamHandler.terminator = '\r'
    for item_id, item in enumerate(iterable, start=1):
        if item_id % interval == 0:
            logger.info(format_str, item_id)
        yield item
    logging.StreamHandler.terminator = prev_terminator
    logger.info("")


def logger_setter(prefix: str = '', subfix: str = ''):
    """
    日志装饰器，用于装饰函数和类。这个装饰器可以为函数或类中的所有方法自动设置日志记录器。
    
    **函数装饰场景**：
    当装饰单个函数时，会为该函数获取或创建一个专用的日志记录器，记录器名称格式为：{prefix}[.{subfix}]。
    在函数执行期间，全局logger会被临时替换为该专用记录器，函数执行完毕后恢复。
    
    **类装饰场景**：
    当装饰类时，会遍历类的所有方法（包括普通方法、静态方法和类方法），为每个方法
    获取或创建日志记录器。每个方法的日志记录器名称格式为：{prefix}[.{subfix}]。
    在方法执行期间，全局logger会被临时替换为该专用记录器，方法执行完毕后恢复。
    
    Args:
        prefix (str, optional): 日志记录器名称的前缀。如果为空，将使用被装饰对象的模块名。
            默认为空字符串。
        subfix (str, optional): 日志记录器名称的后缀。如果提供，将添加到前缀后面。
            默认为空字符串。
            
    Returns:
        decorator: 装饰器函数，接受函数或类作为参数。
        
    Raises:
        ToDoError: 当装饰器应用于不支持的对象类型时抛出。
        
    Examples:
        >>> # 函数装饰场景
        >>> @logger_setter(prefix="msmodelslim.utils.logging")
        >>> def my_function():
        >>>     get_logger().info("函数执行中...")  # 使用 msmodelslim.utils.logging 记录器
        >>>     return "结果"
        
        >>> # 函数装饰场景 - 使用默认模块名
        >>> @logger_setter()
        >>> def another_function():
        >>>     get_logger().info("使用默认日志记录器")  # 使用当前模块名记录器
        
        >>> # 类装饰场景 - 为所有方法设置专用记录器
        >>> @logger_setter(prefix="msmodelslim.utils.logging", subfix="default")
        >>> class MyClass:
        >>>     def method1(self):
        >>>         get_logger().info("方法1执行中...")  # 使用 msmodelslim.utils.logging.default 记录器
        >>>     
        >>>     @staticmethod
        >>>     def static_method():
        >>>         get_logger().info("静态方法执行中...")  # 使用 msmodelslim.utils.logging.default 记录器
        >>>     
        >>>     @classmethod
        >>>     def class_method(cls):
        >>>         get_logger().info("类方法执行中...")  # 使用 msmodelslim.utils.logging.default 记录器
        
        >>> # 类装饰场景 - 使用默认模块名
        >>> @logger_setter()
        >>> class AnotherClass:
        >>>     def method(self):
        >>>         get_logger().info("使用模块名记录器")  # 使用当前模块名 msmodelslim.utils.logging 记录器
    """

    def get_current_logger(obj: Union[Type[Any], Callable]):
        path = prefix
        if not path:
            path = obj.__module__
        if subfix:
            path += f'.{subfix}'
        return get_logger(path)

    def decorator(obj: Union[Type[Any], Callable]):
        if inspect.isclass(obj):
            # 如果是类，直接遍历类的 __dict__ 来获取用户自定义方法
            for name, member in obj.__dict__.items():
                # 过滤掉内置属性
                if name in ['__class__', '__dict__', '__doc__', '__module__', '__weakref__']:
                    continue

                # 检查是否是静态方法或类方法
                if isinstance(member, staticmethod):
                    # 对于静态方法，需要保持其静态方法特性
                    wrapped_func = _apply_logger_context_manager(member.__func__, get_current_logger(obj))
                    setattr(obj, name, staticmethod(wrapped_func))
                elif isinstance(member, classmethod):
                    # 对于类方法，需要保持其类方法特性
                    wrapped_func = _apply_logger_context_manager(member.__func__, get_current_logger(obj))
                    setattr(obj, name, classmethod(wrapped_func))
                elif inspect.isfunction(member):
                    # 普通方法或函数
                    setattr(obj, name, _apply_logger_context_manager(member, get_current_logger(obj)))
            return obj
        elif inspect.isfunction(obj):
            # 如果是函数，应用logger上下文管理器
            return _apply_logger_context_manager(obj, get_current_logger(obj))
        else:
            raise ToDoError(f'decorator only support function or class, not {type(obj)}',
                            action='Please make sure apply logger_setter to a function or class')

    return decorator


@contextmanager
def logger_manager(cur_logger: Logger):
    global logger
    original_logger = logger
    try:
        logger = cur_logger
        yield
    finally:
        logger = original_logger


def _apply_logger_context_manager(func: Callable, target_logger: Logger):
    """为函数应用logger上下文管理器"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        with logger_manager(target_logger):
            return func(*args, **kwargs)

    return wrapper
